#require jQuery
#require Socket.io

#
# Common API : escaping HTML (with jQuery)
#
html_escape(s)->
  obj = document.createElement('pre')
  if obj.textContent?
    obj.textContent = s
  else
    obj.innerText = s
  return obj.innerHTML

class UserInfo
  session_id: null
  user_name: null

#################
# Socket Facade
#################
class SocketHandler
  user_info: null
  socket: null
  player: null

  #
  # constructor
  #
  constructor:(@socket, @player)->
    # Map socket handlers to player
    @socket.onmessage  (message)=>
      switch typeof message.data
        when 'string' then @on_json_message(message.data)
        when 'arraybuffer' then @on_stream_packet(message.data)
        else console.log 'SocketHandler: unknown type:' + typeof message.data
        # @todo error handling

  #
  # socket on receive streaming buffer data
  #
  on_stream_packet:(packet_data)->
    # @todo impl
    throw 'not implemented'

  #
  # socket on receive json data
  #
  on_json_message:(json_data)->
    parsed_data = JSON.parse(json_data)
    switch parsed_data.type
      when 'added_tune'
        tune = new Tune(parsed_data.tune_name, parsed_data.owner_session_id)
        player.on_added_tune(tune)

      else console.log 'SocketHandler: unknown json type:' + parsed_data.type

  #
  # Send add tune request 
  #
  send_add_tune_request:(tune)->
    send_json = 
      type: 'to_add_tune'
      session_id: @user_info.session_id
      tune_name: tune.name
    @send_string(JSON.stringify(send_json))

  #
  # send notify message
  #
  send_string:(str_data)->
    @socket.send(str_data)


#################
# Music file
#################
class Tune
  owner_session_id: null
  name: ''
  audio_buffer: null

  #
  # constructor
  #
  constructor:(file_name, @owner_session_id)->
    @name = html_escape(file_name)

#################
# Music list to play
#################
class TuneList
  tunes: []
  tune_on_playing: null

  #
  # add
  #
  add_tune:(tune)->
    tunes.push(tune)

  #
  # count
  #
  count:->
    @tunes.length

#################
# Abstruct class of player
#################
class Player
  tune_list: new TuneList()
  socket_facade: null

  #
  # constructor
  #
  constructor:()->

  #
  # Message Handler : on receive added tune event from server
  #
  on_added_tune:(tune)->
    # add playlist
    @tune_list.add_tune(tune)

#################
# Plyaer with Web Audio API
#################
class W3CPlayer extends Player
  context: null
  stream_length: 4096
  subscriber_node: null
  observer_node: null
  user_me: null
  tune_on_registering: null
  channel_merger: null

  # check Web Audio API available
  @available_api:-> webkitAudioContext?

  #
  # constructor
  #
  constructor:()->
    super()
    @context = new webkitAudioContext()
    @subscriber_node = @context.createJavaScriptNode(@stream_length, 
      1, @context.destination.numberOfChannels)
    @observer_node = @context.createJavaScriptNode(@stream_length, 
      1, @context.destination.numberOfChannels)
    @channel_merger = @context.createChannelMerger()
    console.log "W3CPlayer::Constructor rate=#{@context.sampleRate} channel=#{@context.destination.numberOfChannels}"

    # on audio proccess
    # called each buffer loaded (buffer size = @stream_length)
    @subscriber_node.onaudioprocess (audio_event)=>
      if !@tune_list.tune_on_playing? then return

      # take one block(@stream_length size)
      all_pcm = @tune_list.tune_on_playing.audio_buffer
      for i in [0..all_pcm.length]
        pcm_block.push(all_pcm[i].shift())

      if (that.audioBuffer[0].length == 0) {
        that.stop();
      } else {
        var msg = AudioMessage.createMessage({
          user_id:AttendeeManager.getUserId(),
          buffer_length:BUFFER_LENGTH,
          buffer_array:buffers
        });
        that.socket.send(msg.buffer);
      } 

      for (var i = 0; i < buffers.length; i++) {
        audio_event.outputBuffer.getChannelData(i).set(buffers[i]);
      }


  #
  # Add my tune to playlist
  #
  add_playlist:(file, on_load_finished)->
    reader = new FileReader()
    reader.onerror = (e)=>
      # @todo error handling
      console.log 'FileReader error :' + e.target.error.code
    reader.onload = (e)=>
      @context.decodeAudioData e.target.result, (buffer)->
        tune = new Tune(file.name, @user_me.session_id)

        # decode 
        pcm_buffer = []
        for ch in [0..buffer.numberOfChannels]
          pcm_buffer[ch] = [];
          for i in [0..buffer.length]
            block = ~~(i/@stream_length)
            offset = i%@stream_length
            if offset is 0
              pcm_buffer[ch][block] = new Float32Array(@stream_length)
            pcm_buffer[ch][block][offset] = buffer.getChannelData(ch)[i]

        tune.audio_buffer = pcm_buffer

        # Add request to server
        @socket_facade.send_add_tune_request(tune)
        @tune_on_registering = tune

        on_load_finished()
      
    reader.readAsArrayBuffer(file);

  #
  # Message Handler : on receive added tune event from server
  #
  on_added_tune:(tune)->
    # overwrite if my registering tune
    if tune.owner_session_id is @user_me.session_id and @tune_on_registering?
      tune = @tune_on_registering
      @tune_on_registering = null

    # add playlist
    super(tune)

  #
  # start play
  #
  start_play:(tune)->
    @tune_list.@tune_on_playing = tune
    # start 'onaudioprocess' cycle
    @subscriber_node.connect(@channel_merger)


#################
# Plyaer with Audio Data API
#################
class MozillaPlayer extends Player
  # check Audio Data API available
  @available_api:-> new Audio().mozWriteAudio?
    

#################
# Factory of player objects
#################
class PlayerFactory
  #
  # create player object
  #
  create:()->
      if W3CPlayer.available_api()
        return new W3CPlayer()

      else if MozillaPlayer.available_api()
#          var audio = new Audio();
#          if (typeof(audio.mozWriteAudio) != "undefined") {
#              var rate = 48000;
#               audio.mozSetup(1, rate);
#              setInterval(function() {
#                  var buf = getbuf(buflen, rate);
#                  audio.mozWriteAudio(buf);
#              }, buflen / rate * 1000);
#         }
      else
        console.log('No audio API is available.')
        return null

